#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
#include <math.h>
#include <mkl.h>
#include <assert.h>
#include <omp.h>

#include <cuda_runtime.h>
#include <cublas_v2.h>
#include <cusolverDn.h>

#ifdef _WIN32
#define EXPORT_SYMBOL __declspec(dllexport)
#else
#define EXPORT_SYMBOL 
#endif

#define NVTX_PROF
#ifdef NVTX_PROF
    #include <nvtx3/nvToolsExt.h>
    #define TIC(label) nvtxRangePushA(label)
    #define TOC() nvtxRangePop()
#else
    // If NVTX_PROF is not defined, provide empty macros
    #define TIC(label) do {} while (0)
    #define TOC() do {} while (0)
#endif

#define CUDA_CHECK(call)                                             \
{                                                                    \
  const cudaError_t error=call;                                      \
  if(error!=cudaSuccess)                                             \
  {                                                                  \
      printf("ERROR: in File %s at Line %d\n",__FILE__,__LINE__);   \
      printf("ERROR Code:%d,reason:%s\n",error,cudaGetErrorString(error)); \
      exit(1);                                                       \
  }                                                                  \
}

#ifndef CUDA_RT_CALL
#define CUDA_RT_CALL( call )                                                                                           \
    {                                                                                                                  \
        auto status = static_cast<cudaError_t>( call );                                                                \
        if ( status != cudaSuccess )                                                                                   \
            fprintf( stderr,                                                                                           \
                     "ERROR: CUDA RT call \"%s\" in line %d of file %s failed "                                        \
                     "with "                                                                                           \
                     "%s (%d).\n",                                                                                     \
                     #call,                                                                                            \
                     __LINE__,                                                                                         \
                     __FILE__,                                                                                         \
                     cudaGetErrorString( status ),                                                                     \
                     status );                                                                                         \
    }
#endif  // CUDA_RT_CALL

enum COMPUTE_STATE{
    INITAL,
    A33_MUL_X3,
    A13_MUL_X3,
    A23_MUL_X3,
    SOLVE_X1X2,
    A31_MUL_X1,
    A32_MUL_X2,
};

enum OPERATION{
    GEMV,
    GETRF,
    GETRS,
};


enum MODE{
    DIRECT,
    CPU_ITERATION,
    GPU_ITERATION,
};

namespace DEVICE {

typedef struct SOLVER_HANDLE {
    cusolverDnHandle_t cudenseH;
    cublasHandle_t cublasH;
    cublasHandle_t streamH[2];
    cudaStream_t streams[2];
    cudaEvent_t events[2];
    double* workspace;
    double* pinned_mem;
    int* devInfo;
    int init;
}SOLVER_HANDLE;

typedef struct FACTOR_HANDLE{
    double* A;
    double* inverse;
    double* fractor;
    double* rhs;
    double* x;
    int* ipiv;
    int last_n = 0;
}FACTOR_HANDLE;

typedef struct MATRIX{
    double* data;
    int n;
    int lda;
}MATRIX;

typedef struct MATRIX_SET{
    MATRIX A13;
    MATRIX A23;
    MATRIX A31;
    MATRIX A32;
    MATRIX A33;
}MATRIX_SET;

typedef struct VECTOR_SET{
    double* x1;
    double* x2;
    double* x3;
}VECTOR_SET;

};

namespace CPU {

typedef struct Global_Info {
    double* matrix;
    double* fractor;    //use the same memory with inverse
    double* inverse;
    double* workplace;
    double* rhs;
    double* x;
    double* scale;
    lapack_int* ipiv;
    int n;
    int last_n;
    int lda;
    int init;
    COMPUTE_STATE compute_state;
    MODE compute_mode;
} Global_Info;

};


